这一节适合于高级用户,
Spring
框架,和想通过
插件开发来配置Grails的开发人员。
实际上Grails是变相的
Spring MVC
应用. Spring MVC是Spring框架内置的
MVC web开发框架.虽然从易用性来说Spring MVC比不上Struts这样的框架,但它的设计和架构都非常优秀,正适合在其基础之上构建另一个像Grails这样的框架。
Grails在以下方面利用了Spring MVC:
也就是说Grails内嵌Spring并在框架的各个环节上使用Spring.
Grails ApplicationContext
Spring开发人员经常热衷于想知道Grails中的ApplicationContext
实例是怎么创建的.基本过程如下:
- Grails通过
web-app/WEB-INF/applicationContext.xml
创建一个父ApplicationContext
对象。
这个ApplicationContext
对象设置
GrailsApplication 对象 and GrailsPluginManager对象.
- 使用这个
ApplicationContext
作为父对象
Grails通过“约定优先”分析 GrailsApplication
对象构建一个子ApplicationContext
对象,此对象作为web应用的 ApplicationContext
配置Spring Beans
大部分Grails的配置都是在运行时进行. 每个 插件 都可以配置在上面创建的ApplicationContext
对象中注册过的Spring bean. For a reference
as to which beans are configured refer to the reference guide which
describes each of the Grails plug-ins and which beans they configure.
使用XML
Beans可用过grails-app/conf/spring/resources.xml
来配置. 这个文件是一个标准的Spring配置文件,在Spring Spring参考文档中对如何配置Spring Beans有详细描述。下面是一个简单的例子:
<bean id="myBean" class="my.company.MyBeanImpl"></bean>
配置完毕后, myBean
就可以在Grails 控制器,标签库,服务等很多地方引用:
class ExampleController { def myBean
}
引用现有的Spring bean
在resources.xml
中声明的bean也可以通过约定来引用Grails类
. 比如, 如果你想在你的bean中引用 BookService
这样一个service,你可以用如下的代码:
<bean id="myBean" class="my.company.MyBeanImpl">
<property name="bookService" ref="bookService" />
</bean>
这个bean本身需要一个 public
setter方法,在Groovy中这样定义:
package my.company
class MyBeanImpl {
BookService bookService
}
或在Java中:
package my.company;
class MyBeanImpl {
private BookService bookService;
public void setBookService(BookService theBookService) {
this.bookService = theBookService;
}
}
既然大部分Grails配置都是在运行时通过约定机制来完成,大部分bean并不需要声明, 但仍然可以在Spring应用中进行引用. 如你需要引用一个Grails DataSource
你可以这样:
<bean id="myBean" class="my.company.MyBeanImpl">
<property name="bookService" ref="bookService" />
<property name="dataSource" ref="dataSource" />
</bean>
或者你需要引用Hibernate SessionFactory
:
<bean id="myBean" class="my.company.MyBeanImpl">
<property name="bookService" ref="bookService" />
<property name="sessionFactory" ref="sessionFactory" />
</bean>
所有提供的bean既说明可参考插件开发文档.
使用Spring DSL
如果你想使用Grails提供的 Spring DSL ,你必须创建grails-app/conf/spring/resources.groovy
文件,定义一个 beans
属性块:
同样在的配置可以应用于XML例子:
beans = {
myBean(my.company.MyBeanImpl) {
bookService = ref("bookService")
}
}
这样做最大的好处是你能够在bean的定义中混合各种逻辑,如基于
environment:
import grails.util.*
beans = {
switch(GrailsUtil.environment) {
case "production":
myBean(my.company.MyBeanImpl) {
bookService = ref("bookService")
} break
case "development":
myBean(my.company.mock.MockImpl) {
bookService = ref("bookService")
}
break
}
}
Grails提供BeanBuilder的目的是提供一种简化的方法来关联使用Spring的各中依赖关系.
这是因为Spring的常规配置方法(通过XML)在本质上是静态的,除了通过程序方式来动态产生XML配置文件外,很难
在运行时修改和添加程序配置。而且这种方法非常繁琐,也容易出错. Grails的BeanBuilder
改变了这一点,它可以让你在运行时通过系统属性和环境属性来动态改变程序逻辑.
这使得程序代码动态适配它的环境,避免不必要的重复代码(如在Spring中为测试环境,开发环境和生产环境做不同的配置)
BeanBuilder 类
Grails提供了 grails.spring.BeanBuilder
类使用动态Groovy来创建bean的声明. 基本点如下:
import org.apache.commons.dbcp.BasicDataSource
import org.codehaus.groovy.grails.orm.hibernate.ConfigurableLocalSessionFactoryBean;
import org.springframework.context.ApplicationContext;def bb = new grails.spring.BeanBuilder()
bb.beans {
dataSource(BasicDataSource) {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:grailsDB"
username = "sa"
password = ""
}
sessionFactory(ConfigurableLocalSessionFactoryBean) {
dataSource = dataSource
hibernateProperties = [ "hibernate.hbm2ddl.auto":"create-drop",
"hibernate.show_sql":true ]
}
}
ApplicationContext appContext = bb.createApplicationContext()
在 插件
和 grails-app/conf/spring/resources.groovy 文件中你不需要创建一个BeanBuilder
实例,
它在 doWithSpring
和 beans
块中都隐式存在.
上面这个例子说明了如果使用 BeanBuilder
类来配置某个特定的Hibernate数据源。
实际上,每个方法调用( dataSource
和 sessionFactory
调用) 都映射到Spring中的bean的名字.
方法的第一个参数是bean的class名字, 最后一个参数是一个块(block). 在块内部可以用标准的Groovy语法设置bean的属性。
通过bean的名字自动查找bean的引用. 通过上面的sessionFactory
bean解析dataSource
可以看点这一点。
也可以通过builder设置一些与bean管理相关的特殊的bean属性,如:
sessionFactory(ConfigurableLocalSessionFactoryBean) { bean ->
bean.autowire = 'byName' // Autowiring behaviour. The other option is 'byType'. [autowire]
bean.initMethod = 'init' // Sets the initialisation method to 'init'. [init-method]
bean.destroyMethod = 'destroy' // Sets the destruction method to 'destroy'. [destroy-method]
bean.scope = 'request' // Sets the scope of the bean. [scope]
dataSource = dataSource
hibernateProperties = [ "hibernate.hbm2ddl.auto":"create-drop",
"hibernate.show_sql":true ]
}
括号中的字符串对应于Spring XML定义中相应的bean 属性名。
在Spring MVC中使用BeanBuilder
如果想在Spring MVC中使用BeanBuilder,你必须确保grails-spring-<version>.jar
包含在classpath中. 还要在/WEB-INF/web.xml
文件中做如下设置:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.groovy</param-value>
</context-param>
<context-param>
<param-name>contextClass</param-name>
<param-value>org.codehaus.groovy.grails.commons.spring.GrailsWebApplicationContext</param-value>
</context-param>
然后在创建
/WEB-INF/applicationContext.groovy文件并配置如下:
beans {
dataSource(org.apache.commons.dbcp.BasicDataSource) {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:grailsDB"
username = "sa"
password = ""
}
}
从文件系统中加载bean定义
你可以使用BeanBuilder
并使用下面的语法 来加载在外部Groovy脚本中定义的bean:
def bb = new BeanBuilder()
bb.loadBeans("classpath:*SpringBeans.groovy")def applicationContext = bb.createApplicationContext()
这里BeanBuilder
将加载在classpath中以SpringBeans.groovy
结尾的Groovy文件并将它们
解析成bean的定义.这里是一个范例脚本文件:
beans {
dataSource(BasicDataSource) {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:grailsDB"
username = "sa"
password = ""
}
sessionFactory(ConfigurableLocalSessionFactoryBean) {
dataSource = dataSource
hibernateProperties = [ "hibernate.hbm2ddl.auto":"create-drop",
"hibernate.show_sql":true ]
}
}
绑定变量
如果从脚本中加载bean,可以通过创建Groovy Binding对象来实现绑定:
def binding = new Binding()
binding.foo = "bar"
def bb = new BeanBuilder()
bb.binding = binding
bb.loadBeans("classpath:*SpringBeans.groovy")
def ctx = bb.createApplicationContext()
使用构建器参数
可以通过在bean的class和最后一个closure之间定义的方法来定义构建器参数:
bb.beans {
exampleBean(MyExampleBean, "firstArgument", 2) {
someProperty = [1,2,3]
}
}
配置 BeanDefinition (使用工厂方法)
传给closure的第一个参数是一个bean配置对象引用,你可以使用它来配置工厂方法,调用 AbstractBeanDefinition 的方法:
bb.beans {
exampleBean(MyExampleBean) { bean ->
bean.factoryMethod = "getInstance"
bean.singleton = false
someProperty = [1,2,3]
}
}
你也可以通过bean 定义方法的返回值来配置bean:
bb.beans {
def example = exampleBean(MyExampleBean) {
someProperty = [1,2,3]
}
example.factoryMethod = "getInstance"
}
使用工厂bean(Factory beans)
Spring提供了工厂bean的概念,即bean不是从class创建,而是由这些工厂创建 defines the concept of factory beans and
often a bean is created not from a class, but from one of these
factories. 在这种情况下bean没有class,你必须将工厂bean的名字传给定义的bean:
bb.beans {
myFactory(ExampleFactoryBean) {
someProperty = [1,2,3]
}
myBean(myFactory) {
name = "blah"
}
}
注意:上面的例子中我们传递的是 myFactory
bean而不是一个clas.
另一个常见的需求是提供调用工厂bean的工厂方法名,可以用下面的Groovy语法做到这一点:
bb.beans {
myFactory(ExampleFactoryBean) {
someProperty = [1,2,3]
}
myBean(myFactory:"getInstance") {
name = "blah"
}
}
这里
ExampleFactoryBean
的 getInstance
会被调用来创建myBean
bean.
运行时创建 bean的引用
有时只有在运行是才知道需要创建的bean的名字. 在这种情况情况下你可以使用字符串替换来实现动态调用:
def beanName = "example"
bb.beans {
"${beanName}Bean"(MyExampleBean) {
someProperty = [1,2,3]
}
}
在这个例子中,使用早先定义的 beanName
变量来调用bean.
另外, 可使用ref
来动态引用在运行时才知道的bean的名字,如下面的代码:
def beanName = "example"
bb.beans {
"${beanName}Bean"(MyExampleBean) {
someProperty = [1,2,3]
}
anotherBean(AnotherBean) {
example = ref("${beanName}Bean")
}
}
这里AnotherBean
属性通过运行时对 exampleBean
的引用来设置
. 也可以通过
ref
来引用在父
ApplicationContext
定义的bean, ApplicationContext
在
BeanBuilder
的构建器中提供:
ApplicationContext parent = ...//
der bb = new BeanBuilder(parent)
bb.beans {
anotherBean(AnotherBean) {
example = ref("${beanName}Bean", true)
}
}
这里第二个参数 true
指定了在父ApplicationContext中查找bean的引用.
使用匿名内部bean
你可以通过将属性块付给bean的一个属性来使用匿名内部bean,这个属性块提供一个bean的类型参数:
bb.beans {
marge(Person.class) {
name = "marge"
husband = { Person p ->
name = "homer"
age = 45
props = [overweight:true, height:"1.8m"]
}
children = [bart, lisa]
}
bart(Person) {
name = "Bart"
age = 11
}
lisa(Person) {
name = "Lisa"
age = 9
}
}
在上面的例子中我们将marge
bean的husband属性 赋值一个属性块(参数类型是Person)的方式创建一个内部bean引用. 如果你有一个工厂bean你也可以忽略类型参数,直接使用
传进进来的bean的定义:
bb.beans {
personFactory(PersonFactory.class)
marge(Person.class) {
name = "marge"
husband = { bean ->
bean.factoryBean = "personFactory"
bean.factoryMethod = "newInstance"
name = "homer"
age = 45
props = [overweight:true, height:"1.8m"]
}
children = [bart, lisa]
}
}
抽象bean和父子bean定义
要创建一个抽象bean,定义一个没有class 的bean:
class HolyGrailQuest {
def start() { println "lets begin" }
}
class KnightOfTheRoundTable {
String name
String leader
KnightOfTheRoundTable(String n) {
this.name = n
}
HolyGrailQuest quest def embarkOnQuest() {
quest.start()
}
}
def bb = new grails.spring.BeanBuilder()
bb.beans {
abstractBean {
leader = "Lancelot"
}
…
}
这里定义了一个抽象bean,这个bean有一个属性 leader
,属性值为
"Lancelot"
.
要使用抽象bean,只要将它设为要定义的bean的父即可:
bb.beans {
…
quest(HolyGrailQuest)
knights(KnightOfTheRoundTable, "Camelot") { bean ->
bean.parent = abstractBean
quest = quest
}
}
当使用父bean时,你必须在设置其他属性前设置parent属性!
如果你要定义一个具有class的抽象bean,可以这样:
def bb = new grails.spring.BeanBuilder()
bb.beans {
abstractBean(KnightOfTheRoundTable) { bean ->
bean.'abstract' = true
leader = "Lancelot"
}
quest(HolyGrailQuest)
knights("Camelot") { bean ->
bean.parent = abstractBean
quest = quest
}
}
上面例子中我们创建了抽象 KnightOfTheRoundTable
并将它的参数设为abstract. 接下来我们定义了一个knights bean,没有
定义它的class,而是继承父bean中定义的class。
使用 Spring命名空间
从Spring 2.0开始,通过XML命名空间可以更方便的使用Spring的各种特性. 如果使用
BeanBuilder, 你可以先声明所要使用的Spring命名空间:
xmlns context:"http://www.springframework.org/schema/context"
然后调用与命名空间名称和属性匹配的方法:
context.'component-scan'( 'base-package' :"my.company.domain" )
通过Spring的命名空间可以做很多有用的事,比如查找JNDI资源:
xmlns jee:"http://www.springframework.org/schema/jee"
jee.'jndi-lookup'(id:"dataSource", 'jndi-name':"java:comp/env/myDataSource")
上面的例子通过查找JNDI创建一个 dataSource
bean对象. 通过Spring命名空间,你可以在BeanBuilder中直接访问Spring AOP功能
比如下面的代码:
class Person {
int age;
String name; void birthday() {
++age;
}
}
class BirthdayCardSender {
List peopleSentCards = []
public void onBirthday(Person person) {
peopleSentCards << person
}
}
你可以定义一个AOP aspect pointcut来监测对 birthday()
方法的所有调用:
xmlns aop:"http://www.springframework.org/schema/aop"
fred(Person) {
name = "Fred"
age = 45
}birthdayCardSenderAspect(BirthdayCardSender)
aop {
config("proxy-target-class":true) {
aspect( id:"sendBirthdayCard",ref:"birthdayCardSenderAspect" ) {
after method:"onBirthday", pointcut: "execution(void ..Person.birthday()) and this(person)"
}
}
}
通过扩展的Spring的PropertyPlaceholderConfigurer,Grails支持属性占位符配置,这和
外部配置配合使用非常有用。
.
Settings defined in either ConfigSlurper
scripts of Java properties files can be used as placeholder values for
Spring configuration in grails-app/conf/spring/resources.xml
.
For example given the following entries in grails-app/conf/Config.groovy
(or an externalized config):
database.driver="com.mysql.jdbc.Driver"
database.dbname="mysql:mydb"
接着在 resources.xml
中用${..}语法定义占位符:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName"><value>${database.driver}</value></property>
<property name="url"><value>jdbc:${database.dbname}</value></property>
</bean>
通过扩展的Spring
PropertyOverrideConfigurer,Grails提供了对属性重载配置的支持,
外部配置配合使用非常有用。
.
你可以提供一个 ConfigSlurper脚本文件,该文件中定义了一个
beans
属性块,属性块中定义的属性值会覆盖bean中定义的属性值:
beans {
bookService.webServiceURL = "http://www.amazon.com"
}
重载的属性应用在Spring ApplicationContext
创建之前. 格式如下:
[bean name].[property name] = [value]
你也可以提供一个常规的Java属性文件,属性文件中的每个条目加上beans
前缀:
beans.bookService.webServiceURL=http://www.amazon.com